#include <stdlib.h>
#include <sys/prctl.h>

#include <i264e_common.h>
//#include <system/system.h>
#ifdef DMALLOC
#include <imp/dmalloc.h>
#endif
typedef struct
{
	void *(*func)(void *);
	void *arg;
	void *ret;
} i264e_threadpool_job_t;

int i264e_sync_elem_list_init(i264e_sync_elem_list_t *slist, int max_size)
{
	if(max_size < 0) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]max_size[%d] less than zero\n",
				__func__, __LINE__, max_size);
		goto err_max_size_lt_zero;
	}
	slist->i_max_size = max_size;
	slist->i_size = 0;
	if((slist->list = malloc((max_size + 1) * sizeof(i264e_elem_t))) == NULL) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]malloc list failed\n",
				__func__, __LINE__);
		goto err_malloc_lst;
	}
	memset(slist->list, 0, (max_size + 1) * sizeof(i264e_elem_t));

	if(i264e_pthread_mutex_init(&slist->mutex, NULL)) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]pthread_mutex_init failed\n",
				__func__, __LINE__);
		goto err_pthread_mutex_init;
	}

	if(i264e_pthread_cond_init(&slist->cv_fill, NULL)) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]pthread_cond_init cv_fill failed\n",
				__func__, __LINE__);
		goto err_pthread_cond_init_cv_fill;
	}

	if(i264e_pthread_cond_init(&slist->cv_empty, NULL)) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]pthread_cond_init cv_empty failed\n",
				__func__, __LINE__);
		goto err_pthread_cond_init_cv_empty;
	}

	return 0;

err_pthread_cond_init_cv_empty:
	i264e_pthread_cond_destroy(&slist->cv_fill);
err_pthread_cond_init_cv_fill:
	i264e_pthread_mutex_destroy(&slist->mutex);
err_pthread_mutex_init:
	free(slist->list);
err_malloc_lst:
err_max_size_lt_zero:
	return -1;
}

void i264e_elem_delete_list(i264e_elem_t *list)
{
	int i = 0;

	if(!list) {
		return;
	}

	while(list[i].p_elem) {
		list[i].elem_free(list[i].p_elem);
		i++;
	}

	free(list);
}

void i264e_sync_elem_list_delete(i264e_sync_elem_list_t *slist)
{
	i264e_pthread_mutex_destroy(&slist->mutex);
	i264e_pthread_cond_destroy(&slist->cv_fill);
	i264e_pthread_cond_destroy(&slist->cv_empty);
	i264e_elem_delete_list(slist->list);
	slist->list = NULL;
}

i264e_elem_t i264e_elem_shift(i264e_elem_t *list)
{
	i264e_elem_t elem = list[0];
	int i;

	for(i = 0; list[i].p_elem; i++) {
		list[i] = list[i+1];
	}
	assert(elem.p_elem);

	return elem;
}

void i264e_sync_elem_list_push(i264e_sync_elem_list_t *slist, i264e_elem_t elem)
{
	i264e_pthread_mutex_lock(&slist->mutex);
	while(slist->i_size == slist->i_max_size) {
		i264e_pthread_cond_wait(&slist->cv_empty, &slist->mutex);
	}
	slist->list[slist->i_size++] = elem;
	i264e_pthread_mutex_unlock(&slist->mutex);
	i264e_pthread_cond_broadcast(&slist->cv_fill);
}

i264e_elem_t i264e_sync_elem_list_pop(i264e_sync_elem_list_t *slist)
{
	i264e_elem_t elem;
	i264e_pthread_mutex_lock(&slist->mutex);
	while(!slist->i_size) {
		i264e_pthread_cond_wait(&slist->cv_fill, &slist->mutex);
	}
	elem = slist->list[--slist->i_size];
	memset(&slist->list[slist->i_size], 0, sizeof(i264e_elem_t));
	i264e_pthread_cond_broadcast(&slist->cv_empty);
	i264e_pthread_mutex_unlock(&slist->mutex);

	return elem;
}

static void *i264e_threadpool_thread(i264e_threadpool_t *pool)
{
	//i264e_log(NULL, C_LOG_INFO, "======THREADID:%s(%d) i264e_threadpool_thread:tid=%ld\n", __func__, __LINE__, mygettid());
	prctl(PR_SET_NAME, __func__);
	if(pool->init_func) {
		pool->init_func(pool->init_arg);
	}

	while(!pool->exit) {
		i264e_elem_t elem;
		i264e_threadpool_job_t *job = NULL;

		i264e_pthread_mutex_lock(&pool->run.mutex);
		while(!pool->exit && !pool->run.i_size) {
			i264e_pthread_cond_wait(&pool->run.cv_fill, &pool->run.mutex);
		}

		if(pool->run.i_size) {
			elem = i264e_elem_shift(pool->run.list);
			job = elem.p_elem;
			pool->run.i_size--;
		}

		i264e_pthread_mutex_unlock(&pool->run.mutex);
		if(!job) {
			continue;
		}
		job->ret = (void*)i264e_stack_align(job->func, job->arg); /* execute the function */
		i264e_sync_elem_list_push(&pool->done, elem);
	}

	return NULL;
}

int i264e_threadpool_init(i264e_threadpool_t **p_pool, int threads,
		void (*init_func)(void *), void *init_arg)
{
	int i = 0;

	if(threads <= 0) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]thread num less than zero\n",
				__func__, __LINE__);
		goto err_threads_lt_zero;
	}

	i264e_threadpool_t *pool;
	if((pool = malloc(sizeof(i264e_threadpool_t))) == NULL) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]malloc pool failed\n",
				__func__, __LINE__);
		goto err_malloc_poll;
	}
	memset(pool, 0, sizeof(i264e_threadpool_t));
	*p_pool = pool;

	pool->init_func = init_func;
	pool->init_arg  = init_arg;
	pool->threads   = threads;

	if((pool->thread_handle = malloc(pool->threads * sizeof(i264e_pthread_t))) == NULL) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]malloc pool failed\n",
				__func__, __LINE__);
		goto err_malloc_thread_handle;
	}
	memset(pool->thread_handle, 0, pool->threads * sizeof(i264e_pthread_t));

	if(i264e_sync_elem_list_init(&pool->uninit, pool->threads)) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]uninit init failed\n",
				__func__, __LINE__);
		goto err_uninit_init;
	}

	if(i264e_sync_elem_list_init(&pool->run, pool->threads)) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]run init failed\n",
				__func__, __LINE__);
		goto err_run_init;
	}

	if(i264e_sync_elem_list_init(&pool->done, pool->threads)) {
		i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]done init failed\n",
				__func__, __LINE__);
		goto err_done_init;
	}

	for(i = 0; i < pool->threads; i++) {
		i264e_elem_t elem;
		i264e_threadpool_job_t *job;
		if ((job = malloc(sizeof(i264e_threadpool_job_t))) == NULL) {
			i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]malloc job[%d] failed\n",
					__func__, __LINE__, i);
			goto err_malloc_job;
		}
		elem.p_elem = job;
		elem.elem_free = free;
		i264e_sync_elem_list_push(&pool->uninit, elem);
	}
	for(i = 0; i < pool->threads; i++) {
		if(i264e_pthread_create(pool->thread_handle+i, NULL, (void*)i264e_threadpool_thread, pool)) {
			i264e_log(NULL, C_LOG_ERROR, "[func:%s,line:%d]create pthread[%d] failed\n",
					__func__, __LINE__, i);
			goto err_pthread_create;
		}
	}

	return 0;

err_pthread_create:
	i264e_pthread_mutex_lock(&pool->run.mutex);
	pool->exit = 1;
	i264e_pthread_cond_broadcast(&pool->run.cv_fill);
	i264e_pthread_mutex_unlock(&pool->run.mutex);
	for (--i; i >= 0; i--) {
		i264e_pthread_join(pool->thread_handle[i], NULL);
	}
err_malloc_job:
	i264e_sync_elem_list_delete(&pool->done);
err_done_init:
	i264e_sync_elem_list_delete(&pool->run);
err_run_init:
	i264e_sync_elem_list_delete(&pool->uninit);
err_uninit_init:
	free(pool->thread_handle);
err_malloc_thread_handle:
	free(pool);
	pool = NULL;
err_malloc_poll:
err_threads_lt_zero:
	return -1;
}

void i264e_threadpool_run(i264e_threadpool_t *pool, void *(*func)(void *), void *arg)
{
	i264e_elem_t elem = i264e_sync_elem_list_pop(&pool->uninit);
	i264e_threadpool_job_t *job = elem.p_elem;
	job->func = func;
	job->arg  = arg;
	i264e_sync_elem_list_push(&pool->run, elem);
}

void *i264e_threadpool_wait(i264e_threadpool_t *pool, void *arg)
{
	int i = 0;
	i264e_elem_t elem;
	i264e_threadpool_job_t *job = NULL;
	void *ret = NULL;

	if (arg == NULL) {
		return NULL;
	}

	i264e_pthread_mutex_lock(&pool->done.mutex);
	while(!job) {
		for(i = 0; i < pool->done.i_size; i++) {
			elem = pool->done.list[i];
			i264e_threadpool_job_t *t = elem.p_elem;
			if(t->arg == arg) {
				elem = i264e_elem_shift(pool->done.list+i);
				job = elem.p_elem;
				pool->done.i_size--;
				break;
			}
		}
		if(!job) {
			i264e_pthread_cond_wait(&pool->done.cv_fill, &pool->done.mutex);
		}
	}
	i264e_pthread_mutex_unlock(&pool->done.mutex);

	ret = job->ret;
	i264e_sync_elem_list_push(&pool->uninit, elem);

	return ret;
}

void *i264e_threadpool_get_done(i264e_threadpool_t *pool, void **donehandler)
{
	i264e_elem_t elem;
	i264e_threadpool_job_t *job = NULL;
	void *ret = NULL;

	i264e_pthread_mutex_lock(&pool->done.mutex);
	while(!job) {
		if (pool->done.i_size) {
			elem = pool->done.list[0];
			job = elem.p_elem;
			break;
		}
		if(!job) {
			i264e_pthread_cond_wait(&pool->done.cv_fill, &pool->done.mutex);
		}
	}
	i264e_pthread_mutex_unlock(&pool->done.mutex);

	*donehandler = job;
	ret = job->arg;

	return ret;
}

int i264e_threadpool_release_done(i264e_threadpool_t *pool, void *donehandler)
{
	i264e_elem_t elem;

	i264e_pthread_mutex_lock(&pool->done.mutex);
	if (pool->done.i_size) {
		elem = i264e_elem_shift(pool->done.list);
		pool->done.i_size--;
	} else {
		i264e_pthread_mutex_unlock(&pool->done.mutex);
		i264e_log(NULL, C_LOG_ERROR, "done.i_size is error\n");
		goto err_done_i_size_is_zero;
	}

	i264e_pthread_mutex_unlock(&pool->done.mutex);

	if (elem.p_elem != donehandler) {
		i264e_log(NULL, C_LOG_ERROR, "released elem:%p not be same as:%p\n", elem.p_elem, donehandler);
		goto err_release_elem_and_donehander_not_same;
	}

	i264e_sync_elem_list_push(&pool->uninit, elem);

	return 0;

err_release_elem_and_donehander_not_same:
err_done_i_size_is_zero:
	return -1;
}

static void i264e_threadpool_list_delete(i264e_sync_elem_list_t *slist)
{
	i264e_sync_elem_list_delete(slist);
	slist = NULL;
}

void i264e_threadpool_delete(i264e_threadpool_t *pool)
{
	int i = 0;

	i264e_pthread_mutex_lock(&pool->run.mutex);
	pool->exit = 1;
	i264e_pthread_cond_broadcast(&pool->run.cv_fill);
	i264e_pthread_mutex_unlock(&pool->run.mutex);
	for(i = 0; i < pool->threads; i++) {
		i264e_pthread_join(pool->thread_handle[i], NULL);
	}

	i264e_threadpool_list_delete(&pool->uninit);
	i264e_threadpool_list_delete(&pool->run);
	i264e_threadpool_list_delete(&pool->done);
	free(pool->thread_handle);
	free(pool);
}
